import dayjs from "dayjs"
import Taro from "@tarojs/taro";
import React, {useState, memo, Component, useEffect} from "react";
import {View, Text, ITouchEvent} from "@tarojs/components";
import "./index.scss"
import {isEqual, transformToRemOrRpx} from "../../utils";

interface LgCalendarVerticalProps {
  selectMode: "single" | "range"
  selectedDay?: string
  canCancel?: boolean
  rangeStart?: string
  rangeEnd?: string
  onSelectedDayChange?: (selectedDay: string) => void
  onRangeChange?: (start: string, end: string) => void
  minDay?: string
  maxDay?: string
}

export const LgCalendarVertical = memo((props: LgCalendarVerticalProps) => {
  //生成前后两个月
  const [currentYear, setCurrentYear] = useState(dayjs(props.selectedDay).year())

  useEffect(() => {
    setCurrentYear(dayjs(props.selectedDay).year())
  }, [props.selectedDay])

  const onYearChange = (val: number) => {
    setCurrentYear(val)
  }

  return (
    <View className="lg-calendar-vertical">
      <View className="year-title">{currentYear}</View>
      <WeekTitle/>
      <ContentBody
        selectMode={props.selectMode}
        selectedDay={props.selectedDay}
        onSelectedDayChange={props.onSelectedDayChange}
        rangeStart={props.rangeStart}
        rangeEnd={props.rangeEnd}
        onRangeChange={props.onRangeChange}
        // onMonthChange={onMonthChange}
        minDay={props.minDay}
        maxDay={props.maxDay}
        onYearChange={onYearChange}
        canCancel={props.canCancel === undefined ? true : props.canCancel}/>
    </View>
  )
})


/**
 * 创建日期组件的周
 * @returns JSX.Element
 */
const WeekTitle = memo(() => {
  return (
    <View className="week-title">
      <View className="week-item">日</View>
      <View className="week-item">一</View>
      <View className="week-item">二</View>
      <View className="week-item">三</View>
      <View className="week-item">四</View>
      <View className="week-item">五</View>
      <View className="week-item">六</View>
    </View>
  )
})

// 在当前日期所在月后再加载START_INIT_MONTH个月份
const HEAD_INIT_MONTH = 2
const Tail_INIT_MONTH = 2

// 如果接近底部，或者解决顶部，月份小于或等于LOAD_IF_LESS时加载LOAD_MONTH个月份
const LOAD_MONTH = 1

type MonthType = {
  year: number;
  month: number;
  monthLines: number; //当前月分有多少行
};

interface ContentBodyState {
  months: Array<MonthType>
  scrollTop: number
  selectedDay: string
  rangeStart: string
  rangeEnd: string
  transition: string
}

export interface ContentBodyProps {
  selectMode: "single" | "range"
  selectedDay?: string //格式 YYYY-MM-DD
  canCancel: boolean
  rangeStart?: string
  rangeEnd?: string
  onMonthChange?: (index: number) => void
  onYearChange?: (index: number) => void
  onSelectedDayChange?: (selectedDay: string) => void
  onRangeChange?: (start: string, end: string) => void
  minDay?: string
  maxDay?: string
}


let contentBodyId = 0

/**
 * 渲染月份并添加滚动条相关事件
 * @param props
 * @returns
 */
class ContentBody extends Component<ContentBodyProps, ContentBodyState> {

  private isLoadingHead: boolean
  private isLoadingTail: boolean

  private readonly id: string

  private dayHeight: number
  private monthTitleHeight: number
  private touchStartY: number
  private currentIndex: number

  private ruler: Array<number>
  private loadThreshold: number
  private minDay: string
  private maxDay: string

  constructor(props) {
    super(props)
    let currentDay = dayjs()
    if (this.props.selectMode === "single" && this.props.selectedDay) {
      currentDay = dayjs(this.props.selectedDay)
    } else if (this.props.selectMode === "range" && this.props.rangeStart) {
      currentDay = dayjs(this.props.rangeStart)
    }

    this.isLoadingHead = false
    this.isLoadingTail = false
    this.id = `__content-body__${contentBodyId++}`
    this.dayHeight = 0
    this.monthTitleHeight = 0
    this.touchStartY = 0
    this.ruler = []
    this.minDay = this.props.minDay || ""
    this.maxDay = this.props.maxDay || ""

    //init currentIndex
    this.initCurrentIndex(currentDay)

    this.state = {
      months: this.createMonth(currentDay),
      scrollTop: 0,
      selectedDay: this.props.selectMode === "single" ? `${currentDay.year()}-${currentDay.month() + 1}-${currentDay.date()}` : "",
      rangeStart: this.props.selectMode === "range" && this.props.rangeStart ? this.props.rangeStart : "",
      rangeEnd: this.props.selectMode === "range" && this.props.rangeEnd ? this.props.rangeEnd : "",
      transition: "none"
    }

    //由于onScroll事件触发频繁，因此转为定时触发
    this.dayClick = this.dayClick.bind(this)
    this.onTouchStart = this.onTouchStart.bind(this)
    this.onTouchMove = this.onTouchMove.bind(this)
    this.onTouchEnd = this.onTouchEnd.bind(this)
  }

  initCurrentIndex(centerDayjs: dayjs.Dayjs) {
    this.currentIndex = HEAD_INIT_MONTH
    if (this.minDay) {
      const minDayjs = dayjs(this.minDay)
      let months = Math.abs(minDayjs.diff(centerDayjs, "month"))
      if (this.currentIndex > months) {
        this.currentIndex = months
      }
    }
  }

  UNSAFE_componentWillReceiveProps(nextProps: ContentBodyProps) {
    this.minDay = nextProps.minDay || ""
    this.maxDay = nextProps.maxDay || ""
    if (this.props.selectMode == nextProps.selectMode) {
      if (this.props.selectedDay != nextProps.selectedDay) {
        this.setState({
          selectedDay: nextProps.selectedDay ? nextProps.selectedDay : ""
        })
        if (nextProps.selectedDay) {
          const newMonths = this.createMonth(nextProps.selectedDay)
          this.initCurrentIndex(dayjs(nextProps.selectedDay))
          this.setState({
            months: [...newMonths],
          }, () => {
            this.updateRuler()
            this.setState({
              scrollTop: this.currentIndex <= 0 ? 0 : this.ruler[this.currentIndex - 1]
            })
          })
        }
      }

      if (this.props.rangeStart != nextProps.rangeStart) {
        this.setState({
          rangeStart: nextProps.rangeStart ? nextProps.rangeStart : ""
        })
        if (nextProps.selectedDay) {
          const newMonths = this.createMonth(nextProps.selectedDay)
          this.initCurrentIndex(dayjs(nextProps.selectedDay))
          this.setState({
            months: [...newMonths]
          }, () => {
            this.updateRuler()
            this.setState({
              scrollTop: this.ruler[this.currentIndex - 1]
            })
          })
        }
      }

      if (this.props.rangeEnd != nextProps.rangeEnd) {
        this.setState({
          rangeEnd: nextProps.rangeEnd ? nextProps.rangeEnd : ""
        })
      }
    } else {
      if (nextProps.selectMode == "single") {
        this.setState({
          selectedDay: nextProps.selectedDay ? nextProps.selectedDay : "",
          rangeStart: "",
          rangeEnd: ""
        })
        if (nextProps.selectedDay) {
          const newMonths = this.createMonth(dayjs(nextProps.selectedDay))
          this.initCurrentIndex(dayjs(nextProps.selectedDay))
          this.setState({
            months: [...newMonths]
          }, () => {
            this.updateRuler()
            this.setState({
              scrollTop: this.ruler[this.currentIndex - 1]
            })
          })
        }
      } else {
        this.setState({
          selectedDay: "",
          rangeStart: nextProps.rangeStart ? nextProps.rangeStart : "",
          rangeEnd: nextProps.rangeEnd ? nextProps.rangeEnd : ""
        })
        if (nextProps.selectedDay) {
          const newMonths = this.createMonth(dayjs(nextProps.selectedDay))
          this.initCurrentIndex(dayjs(nextProps.selectedDay))
          this.setState({
            months: [...newMonths]
          }, () => {
            this.updateRuler()
            this.setState({
              scrollTop: this.ruler[this.currentIndex - 1]
            })
          })
        }
      }
    }
  }

  createMonth(centerDay: string | dayjs.Dayjs) {
    const newMonths = new Array<MonthType>()
    centerDay = dayjs.isDayjs(centerDay) ? centerDay : dayjs(centerDay)
    const centerMonth = centerDay.startOf("month")

    let headInitMonth = HEAD_INIT_MONTH
    if (this.minDay) {
      const minDayjs = dayjs(this.minDay)
      const months = Math.abs(centerDay.diff(minDayjs, "month"))
      if (headInitMonth > months) {
        headInitMonth = months
      }
    }
    let preMonth = centerMonth;
    for (let i = 0; i < headInitMonth; i++) {
      preMonth = preMonth.add(-1, "month")
      newMonths.unshift({year: preMonth.year(), month: preMonth.month() + 1, monthLines: this.getMonthLine(preMonth)})
    }

    newMonths.push({
      year: centerMonth.year(),
      month: centerMonth.month() + 1,
      monthLines: this.getMonthLine(centerMonth)
    })

    let tailInitMonth = Tail_INIT_MONTH

    if (this.maxDay) {
      const maxDayjs = dayjs(this.maxDay)
      const months = Math.abs(maxDayjs.diff(centerMonth, "month"))
      if (tailInitMonth > months) {
        tailInitMonth = months
      }
    }

    let nextMonth = centerMonth;
    for (let i = 0; i < tailInitMonth; i++) {
      nextMonth = nextMonth.add(1, "month")
      newMonths.push({year: nextMonth.year(), month: nextMonth.month() + 1, monthLines: this.getMonthLine(nextMonth)})
    }
    return newMonths
  }

  componentDidMount() {
    setTimeout(() => {
      Taro.createSelectorQuery()
        .select(`#${this.id} .month-body .month-title`)
        .boundingClientRect(res => {
          this.monthTitleHeight = res.height
        }).exec(() => {
      })
      Taro.createSelectorQuery()
        .select(`#${this.id} .month-day .day`)
        .boundingClientRect(res => {
          this.dayHeight = res.height
          this.updateRuler()
          this.loadThreshold = this.monthTitleHeight + this.dayHeight * 3
          this.setState({
            scrollTop: this.currentIndex > 0 ? this.ruler[this.currentIndex - 1] : 0
          })
        }).exec(() => {
      })
    }, 200);
  }

  updateRuler() {
    this.ruler = []
    for (let i = 0; i < this.state.months.length; i++) {
      const lastHeight = i == 0 ? 0 : this.ruler[i - 1]
      this.ruler.push(lastHeight + this.monthTitleHeight + this.state.months[i].monthLines * this.dayHeight)
    }
  }

  getMonthLine(monthDay: dayjs.Dayjs) {
    const totalCell = monthDay.daysInMonth() + monthDay.day()
    return Math.ceil(totalCell / 7)
  }

  //加载尾部
  loadTailMonth(nextScrollTop: number) {
    if (this.isLoadingTail) {
      return
    }

    let loadMonth = LOAD_MONTH;

    //判断是否已经到达最大值的月份
    if (this.maxDay) {
      const maxDayjs = dayjs(this.maxDay).startOf("month");
      const currentMaxMonth = this.state.months[this.state.months.length - 1]
      const currentMaxDayjs = dayjs(`${currentMaxMonth.year}-${currentMaxMonth.month}-01`)
      if (currentMaxDayjs.isSame(maxDayjs) || currentMaxDayjs.isAfter(maxDayjs)) {
        if (nextScrollTop > this.ruler[this.ruler.length - 2]) {
          nextScrollTop = this.ruler[this.ruler.length - 2]
        }
        this.setState({
          scrollTop: nextScrollTop
        })
        return;
      }

      const remainMonths = Math.abs(maxDayjs.diff(currentMaxDayjs, "month"))
      if (loadMonth > remainMonths) {
        loadMonth = remainMonths
      }
    }

    this.isLoadingTail = true

    let months = this.state.months
    for (let i = 0; i < loadMonth; i++) {
      let nextMonth = dayjs(`${months[months.length - 1].year}-${months[months.length - 1].month}-01`).add(1, "month")
      months.push({year: nextMonth.year(), month: nextMonth.month() + 1, monthLines: this.getMonthLine(nextMonth)})
    }
    this.updateRuler()
    this.setState({
      months: [...months],
      scrollTop: nextScrollTop
    }, () => {
      this.updateRuler()
      this.isLoadingTail = false
    })
  }


  //加载头部
  loadHeadMonth(nextScrollTop: number) {
    if (this.isLoadingHead) {
      return
    }

    let loadMonth = LOAD_MONTH;
    //判断是否已经到达最小值的月份
    if (this.minDay) {
      const minDayjs = dayjs(this.minDay).endOf("month");
      const currentMinMonth = this.state.months[0]
      const currentMinDayjs = dayjs(`${currentMinMonth.year}-${currentMinMonth.month}-01`).endOf("month")
      if (currentMinDayjs.isSame(minDayjs) || currentMinDayjs.isBefore(minDayjs)) {
        if (nextScrollTop || nextScrollTop === 0) {
          this.setState({
            scrollTop: nextScrollTop,
            transition: "transform 0.25s"
          })
        }
        return;
      }

      const remainMonths = Math.abs(currentMinDayjs.diff(minDayjs, "month"))
      if (loadMonth > remainMonths) {
        loadMonth = remainMonths
      }
    }

    this.isLoadingHead = true

    let months = this.state.months
    for (let i = 0; i < loadMonth; i++) {
      let preMonth = dayjs(`${months[0].year}-${months[0].month}-01`).add(-1, "month")
      const monthLine = this.getMonthLine(preMonth);
      months.unshift({year: preMonth.year(), month: preMonth.month() + 1, monthLines: monthLine})
      nextScrollTop += this.monthTitleHeight + monthLine * this.dayHeight
    }

    //由于数组添加了元素 this.currentIdex的值必须改变
    this.currentIndex += loadMonth
    this.setState({
      months: [...months],
      scrollTop: nextScrollTop,
      transition: "none"
    }, () => {
      this.updateRuler()
      this.isLoadingHead = false
    })
  }


  dayClick(param: DayParam) {

    const currentClickDay = `${param.year}-${param.month}-${param.day}`;

    if (this.props.selectMode == "single") {
      if (param.selected) {
        if (this.props.canCancel) {
          this.setState({
            selectedDay: ""
          }, () => {
            if (this.props.onSelectedDayChange) {
              this.props.onSelectedDayChange("")
            }
          })
        }
      } else {
        this.setState({
          selectedDay: currentClickDay
        }, () => {
          if (this.props.onSelectedDayChange) {
            this.props.onSelectedDayChange(currentClickDay)
          }
        })
      }
    } else if (this.props.selectMode == "range") {
      if (this.state.rangeEnd || !this.state.rangeStart || dayjs(currentClickDay).isBefore(this.state.rangeStart)) {
        this.setState({
          rangeStart: currentClickDay,
          rangeEnd: ""
        }, () => {
          if (this.props.onRangeChange) {
            this.props.onRangeChange(currentClickDay, "")
          }
        })
      } else {
        this.setState({
          rangeEnd: currentClickDay
        }, () => {
          if (this.props.onRangeChange) {
            this.props.onRangeChange(this.state.rangeStart, currentClickDay)
          }
        })
      }
    }
  }

  onTouchStart(event: ITouchEvent) {
    if (this.isLoadingHead || this.isLoadingTail) {
      return
    }
    this.touchStartY = event.changedTouches[0].screenY || event.changedTouches[0].pageY
    this.setState({
      transition: "none"
    })
  }

  onTouchMove(event: ITouchEvent) {
    const moveY = event.changedTouches[0].screenY || event.changedTouches[0].pageY

    const distance = moveY - this.touchStartY

    let newScrollTop = this.state.scrollTop - distance

    this.touchStartY = moveY

    if (this.isLoadingHead || this.isLoadingTail) {
      return
    }
    this.checkIndexChange(newScrollTop);

    if (newScrollTop < this.loadThreshold && distance > 0) {
      this.loadHeadMonth(newScrollTop)
    } else if (newScrollTop > this.ruler[this.ruler.length - 2] - this.loadThreshold && distance < 0) {
      this.loadTailMonth(newScrollTop)
    } else {
      if (newScrollTop < 0) {
        newScrollTop = 0
      }
      if (newScrollTop > this.ruler[this.ruler.length - 2]) {
        newScrollTop = this.ruler[this.ruler.length - 2]
      }
      this.setState({
        scrollTop: newScrollTop,
        transition: "transform 0.25s"
      })
    }
  }

  onTouchEnd(_event: ITouchEvent) {
    // this.setState({
    //     transition: "all 0.2s"
    // })
  }

  render() {
    let translateY = transformToRemOrRpx(this.state.scrollTop)
    return (
      <View
        id={this.id}
        className="content-body">
        <View
          onTouchStart={this.onTouchStart}
          onTouchMove={this.onTouchMove}
          onTouchEnd={this.onTouchEnd}
          className="content-body-panel"
          style={{transform: `translateY(-${translateY})`, transition: this.state.transition}}
        >
          {
            this.state.months.map(item => {
              return (
                <MonthBody dayClick={this.dayClick}
                           key={`${item.year}-${item.month}`}
                           year={item.year}
                           month={item.month}
                           selectedDay={this.state.selectedDay}
                           rangeStart={this.state.rangeStart}
                           rangeEnd={this.state.rangeEnd}
                           minDay={this.minDay}
                           maxDay={this.maxDay}
                />
              )
            })
          }
        </View>
      </View>
    )
  }

  private checkIndexChange(newScrollTop: number) {
    let target = newScrollTop
    //通过二分查找ruler数组，获取当前所在月份
    let start = 0
    let end = this.ruler.length - 1
    let index = 0
    while (true) {
      index = Math.ceil((end - start) / 2) + start

      if (index === 0) {
        if (target >= 0 || target <= this.ruler[index]) {
          break;
        }
        return;
      }

      if (target > this.ruler[index - 1] && target <= this.ruler[index]) {
        break;
      } else if (target > this.ruler[index]) {
        start = index + 1
      } else {
        end = index - 1
      }
      if (end < start) {
        break
      }
    }

    if (this.currentIndex != index) {
      this.props.onMonthChange && this.props.onMonthChange(this.state.months[index].month)
      this.props.onYearChange && this.props.onYearChange(this.state.months[index].year)
    }
    this.currentIndex = index

  }
}

export type DayParam = {
  year: number,
  month: number,
  day: number,
  selected: boolean
}

interface MonthBodyProps {
  year: number
  month: number
  selectedDay?: string
  dayClick?: (param: DayParam) => void
  rangeStart?: string
  rangeEnd?: string
  minDay?: string
  maxDay?: string
}

//计算不同星期的1号paddingLeft值
const dayPaddings = new Array<number>()
for (let i = 0; i < 7; i++) {
  dayPaddings.push(i * 100 / 7)
}

// 计算每个月份的paddingLeft值
let monthPaddings = dayPaddings.map(item => item + 1)

class MonthBody extends Component<MonthBodyProps> {
  private monthDays: Array<number>
  private minDay: string
  private maxDay: string

  constructor(props) {
    super(props)
    this.monthDays = new Array<number>()
    this.minDay = this.props.minDay || ""
    this.maxDay = this.props.maxDay || ""
    const daysInMonth = dayjs(`${props.year}-${props.month}-01`).daysInMonth()
    for (let i = 1; i <= daysInMonth; i++) {
      this.monthDays.push(i)
    }
  }

  shouldComponentUpdate(nextProps: MonthBodyProps) {
    let preProps = this.props
    this.minDay = nextProps.minDay || ""
    this.maxDay = nextProps.maxDay || ""
    if (preProps.minDay !== nextProps.minDay || preProps.maxDay != nextProps.maxDay) {
      return true
    }

    if (isEqual(preProps, nextProps)) {
      return false
    }
    if (preProps.dayClick == nextProps.dayClick && preProps.year == nextProps.year && preProps.month == nextProps.month) {
      const year = preProps.year
      const month = preProps.month
      if (preProps.selectedDay == nextProps.selectedDay && preProps.rangeStart == nextProps.rangeStart && preProps.rangeEnd == nextProps.rangeEnd) {
        return false
      }

      if (preProps.selectedDay !== nextProps.selectedDay) {
        if (preProps.selectedDay === "") {
          let nextSelectDay = dayjs(nextProps.selectedDay)
          return year === nextSelectDay.year() && month === nextSelectDay.month() + 1
        }

        if (nextProps.selectedDay === "") {
          let preSelectDay = dayjs(preProps.selectedDay)
          return year === preSelectDay.year() && month === preSelectDay.month() + 1
        }

        let preSelectDay = dayjs(preProps.selectedDay)
        if (preSelectDay.year() === year && preSelectDay.month() + 1 === month) {
          return true
        }
        let nextSelectDay = dayjs(nextProps.selectedDay)
        return nextSelectDay.year() === year && nextSelectDay.month() + 1 === month;
      }

      if (preProps.rangeStart !== nextProps.rangeStart || preProps.rangeEnd !== nextProps.rangeEnd) {
        return inRange(year, month, preProps.rangeStart, preProps.rangeEnd)
          || inRange(year, month, nextProps.rangeStart, nextProps.rangeEnd)
      }

    }

    return true
  }

  dayClick(param: DayParam) {
    if (this.props.dayClick && this.dayCanClick(`${param.year}-${param.month}-${param.day}`)) {
      this.props.dayClick(param)
    }
  }

  dayCanClick(currentClickDay: string): boolean {
    if (this.minDay) {
      const currentClickDayjs = dayjs(currentClickDay)
      if (currentClickDayjs.isBefore(this.minDay)) {
        return false
      }
    }

    if (this.maxDay) {
      const currentClickDayjs = dayjs(currentClickDay)
      if (currentClickDayjs.isAfter(this.maxDay)) {
        return false
      }
    }

    return true
  }

  render() {
    let monthTitleEl: React.ReactNode = null
    const today = dayjs()
    if (this.props.month) {
      monthTitleEl = <View style={{
        marginLeft: monthPaddings[dayjs(`${this.props.year}-${this.props.month}-01`).day()] + "%",
        color: today.year() == this.props.year && today.month() + 1 === this.props.month ? "#0089fe" : "#000"
      }} className="month-title"><Text className="month-title-text">{this.props.month}月</Text></View>
    }
    return (
      <View id={`${this.props.year}-${this.props.month}`} className="month-body">
        {monthTitleEl}
        <View className="month-day">
          {
            this.monthDays.map(item =>
              <View key={`${this.props.year}-${this.props.month}-${item}`}
                    style={{marginLeft: item == 1 ? (dayPaddings[dayjs(`${this.props.year}-${this.props.month}-${item}`).day()] + "%") : "0"}}
                    className="day" onClick={() => this.dayClick({
                year: this.props.year,
                month: this.props.month,
                day: item,
                selected: this.props.selectedDay === `${this.props.year}-${this.props.month}-${item}`
              })}>
                <DayRender canClick={this.dayCanClick(`${this.props.year}-${this.props.month}-${item}`)}
                           rangeStart={this.props.rangeStart ? dayjs(this.props.rangeStart) : null}
                           rangeEnd={this.props.rangeEnd ? dayjs(this.props.rangeEnd) : null} year={this.props.year}
                           month={this.props.month} day={item}
                           selected={this.props.selectedDay === `${this.props.year}-${this.props.month}-${item}`}/>
              </View>
            )
          }
        </View>
      </View>
    )
  }
}


//判断年月是否 rangeStart和rangeEnd范围内
function inRange(year: number, month: number, rangeStart?: string, rangeEnd?: string) {
  if (!rangeStart && !rangeEnd) {
    return false
  }

  if (rangeStart && !rangeEnd) {
    const rangeStartDayjs = dayjs(rangeStart)
    return year === rangeStartDayjs.year() && month === rangeStartDayjs.month() + 1
  }

  if (!rangeStart && rangeEnd) {
    const rangeEndDayjs = dayjs(rangeEnd)
    return year === rangeEndDayjs.year() && month === rangeEndDayjs.month() + 1
  }

  const rangeStartDayjs = dayjs(rangeStart).startOf("month")
  const rangeEndDayjs = dayjs(rangeEnd).startOf("month")
  const current = dayjs(`${year}-${month}-01`)
  return (rangeStartDayjs.isSame(current) || rangeStartDayjs.isBefore(current))
    && (rangeEndDayjs.isSame(current) || rangeEndDayjs.isAfter(current))
}

interface DayRenderProps {
  year: number
  month: number
  day: number
  selected: boolean
  rangeStart: dayjs.Dayjs | null
  rangeEnd: dayjs.Dayjs | null
  canClick: boolean
}

class DayRender extends Component<DayRenderProps> {

  shouldComponentUpdate(nextProps: DayRenderProps) {
    const preProps = this.props
    if (preProps.canClick != nextProps.canClick) {
      return true
    }
    if (isEqual(preProps, nextProps)) {
      return false
    }
    if (preProps.year === nextProps.year && preProps.month == nextProps.month && preProps.day === nextProps.day) {

      if (preProps.rangeEnd !== nextProps.rangeEnd || preProps.rangeStart !== nextProps.rangeStart) {
        return dayInRange(preProps.year, preProps.month, preProps.day, preProps.rangeStart, preProps.rangeEnd)
          || dayInRange(nextProps.year, nextProps.month, nextProps.day, nextProps.rangeStart, nextProps.rangeEnd)
      }

      if (preProps.selected === nextProps.selected) {
        return false
      }
    }
    return true
  }

  render() {
    const today = dayjs()
    const tomorrow = today.add(1, "day")
    const current = dayjs(`${this.props.year}-${this.props.month}-${this.props.day}`)
    const classNames = new Array<string>()
    const weekDay = current.day()
    if (weekDay === 0 || weekDay === 6) {
      classNames.push("week-day")
    }
    if (this.props.selected) {
      classNames.push("selected")
    }

    if (this.props.rangeStart && current.isSame(this.props.rangeStart)) {
      classNames.push("range-start")
    }

    if (this.props.rangeEnd && current.isSame(this.props.rangeEnd)) {
      classNames.push("range-end")
    }

    if (this.props.rangeStart && this.props.rangeEnd
      && (!classNames.includes("range-start") || !classNames.includes("range-end"))
      && current.isAfter(this.props.rangeStart) && current.isBefore(this.props.rangeEnd)) {
      classNames.push("range-middle")
    }

    if (!this.props.canClick) {
      classNames.push("disabled-day")
    }

    const isToday = current.year() === today.year() && current.month() == today.month() && current.date() == today.date()
    const isTomorrow = current.year() === tomorrow.year() && current.month() == tomorrow.month() && current.date() == tomorrow.date()
    if (isToday || isTomorrow) {
      classNames.push("double-line")
      return (
        <View className={classNames.join(" ")}>
          <View>
            {this.props.day}
          </View>
          <View>
            {isToday ? "今天" : "明天"}
          </View>
        </View>
      )
    } else {
      return (
        <View className={classNames.join(" ")}>
          {this.props.day}
        </View>
      )
    }
  }
}

function dayInRange(year: number, month: number, day: number, rangeStart: dayjs.Dayjs | null, rangeEnd: dayjs.Dayjs | null) {
  if (!rangeStart && !rangeEnd) {
    return false
  }

  const current = `${year}-${month}-${day}`

  if (rangeStart && !rangeEnd) {
    return rangeStart.isSame(current)
  }
  if (!rangeStart && rangeEnd) {
    return rangeEnd.isSame(current)
  }
  if (rangeStart && rangeEnd) {
    return (rangeStart.isSame(current) || rangeStart.isBefore(current))
      && (rangeEnd.isSame(current) || rangeEnd.isAfter(current))
  }
  return false
}
